Supabaseでservice_roleを使わずanon+RLS+SSR_EdgeFunctions で構成する
作成日
更新日
#ChatGPTより引用
素晴らしい判断です。service_role
を使わずに anon
キー + RLS + SSR / Edge Functions で構成するアーキテクチャは、Supabase をセキュアかつスケーラブルに運用する現代的なベストプラクティスです。
✅ ゴールの構成
[Client] ────▶ [Deno Fresh SSR or EdgeFunction] ────▶ Supabase (anon key + RLS enforced)
要素 | 内容 |
---|---|
🔐 anon キー |
パブリック用、SupabaseにRLS付きで最小限アクセスさせる |
🛡️ RLS(Row Level Security) | private = false など行単位で公開制御 |
⚙️ SSR / Edge Function | クライアントからはDB接続を隠蔽。Fresh内でfetch or Supabase SDKを使って中継 |
❌ service_role |
一切使わない(リーク対策・アクセス最小化) |
✅ ステップごとの構成方法
✅ ステップ 1:Supabase 側の RLS ポリシー設定
例:articles
テーブルを「private = false
の記事だけ誰でも取得可能」に
-- RLSを有効化
alter table articles enable row level security;
-- 誰でも読めるポリシー(anonキーでもOK)
create policy "Public access to non-private articles"
on articles
for select
using (private = false);
✅
anon
キーはデフォルトでauthenticated
ロールになるので、auth.uid()
は使えません(public用途なら問題なし)
✅ ステップ 2:環境変数に anon
キーだけを保持
.env
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOi...(anon key)
✅ ステップ 3:Fresh SSR 側で Supabase を呼び出す
例:routes/index.tsx
にて SSR で Supabase から記事一覧取得
// routes/index.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
import { createClient } from "supabase";
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const supabase = createClient(supabaseUrl, supabaseAnonKey);
export const handler: Handlers = {
async GET(req, ctx) {
const { data, error } = await supabase
.from("articles")
.select("title, slug, created_at")
.eq("private", false)
.order("created_at", { ascending: false });
if (error) console.error(error);
return ctx.render({ articles: data ?? [] });
},
};
export default function HomePage({ data }: PageProps<{ articles: any[] }>) {
return (
<div>
<h1>Articles</h1>
<ul>
{data.articles.map((article) => (
<li key={article.slug}>{article.title}</li>
))}
</ul>
</div>
);
}
👉 クライアントから直接Supabaseへアクセスしていないので、漏洩リスクなし。
✅ ステップ 4(任意):Edge Functionでの中継にする(API風)
routes/api/articles.ts
import { Handlers } from "$fresh/server.ts";
import { createClient } from "supabase";
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!
);
export const handler: Handlers = {
async GET(req) {
const { data, error } = await supabase
.from("articles")
.select("title, slug, created_at")
.eq("private", false)
.order("created_at", { ascending: false });
return new Response(JSON.stringify(data ?? []), {
headers: { "content-type": "application/json" },
});
},
};
→ クライアントは /api/articles
を叩くだけ。Supabaseへの直接アクセスは一切なし。
✅ この構成のメリット
メリット | 内容 |
---|---|
🔒 セキュア | service_role 不使用、anon + RLSだけで安全運用 |
👁️🗨️ アプリにキーが露出しない | クライアントには anon すら渡さない |
⚙ SSR対応 | Freshの静的化・キャッシュと相性良好 |
🧱 スケーラブル | Edge Function構成で将来API分離も可能 |
✅ まとめ:今後の運用設計の型
📁 .env
SUPABASE_URL
SUPABASE_ANON_KEY
📁 routes/
index.tsx ← SSRでSupabase叩く
api/articles.ts ← Edge Functionでも対応可(SPA用途)
📁 supabase/
RLSポリシーで制御(service_role使わない)
✅ middleware.ts ← Bot防御・旧URL対策など
必要であれば:
- RLSをロール別に拡張する例(user_idベース)
- コメント付きSupabase設計スキーマ
- Supabase Authと組み合わせたJWTバインド構成
などもご案内できます。ぜひお気軽に!

公開日
更新日